#!/usr/bin/env python3

# Copyright 2017 CodiLime
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import importlib
import inspect
from os import path

import six

from veles.schema import enumeration, model


def generate_cpp_code():
    if six.PY2:
        raise RuntimeError('C++ code can only be generated on Python 3.x')
    parser = argparse.ArgumentParser()
    parser.add_argument('destination')
    parser.add_argument('source_modules', nargs='+')
    args = parser.parse_args()
    license = '''/*
 * Copyright 2017 CodiLime
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
// This file is autogenerated do not modify it by hand
'''
    header_code = license
    header_code += '''
#pragma once
#include <memory>

#include "fwd_models.h"
#include "data/bindata.h"
#include "data/nodeid.h"
#include "network/msgpackobject.h"
'''
    source_code = license
    source_code += '''#include "models.h"
'''
    fwd_header_code = license + '''#pragma once
namespace veles {
namespace messages {
class MsgpackObject;
}  // namespace messages
}  // namespace veles
'''
    deps = set()
    sources = []
    # TODO create DAG of modules here instead of relying that they are sorted
    for module_name in args.source_modules:
        module = importlib.import_module(module_name)
        namespaces = module_name.split('.')
        deps.update(get_deps(module))
        fwd_header, header, source = generate_from_file(module, namespaces)
        fwd_header_code += fwd_header
        header_code += header
        sources.append(source)
    source_code += ''.join('#include "{}"\n'.format(i) for i in deps)
    source_code += ''.join(sources)
    with open(path.join(args.destination, 'fwd_models.h'), 'w') as f:
        f.write(fwd_header_code)
    with open(path.join(args.destination, 'models.h'), 'w') as f:
        f.write(header_code)
    with open(path.join(args.destination, 'models.cc'), 'w') as f:
        f.write(source_code)


def get_deps(source):
    deps = set()
    for el in getattr(source, '_models', []):
        if len(el.cpp_type()) > 2:
            deps.add(el.cpp_type()[2])
    return deps


def generate_from_file(source, namespaces):
    namespace_specific_start = ''.join(
        ['namespace {} {{\n'.format(i) for i in namespaces[:-1]])
    namespace_messages_start = '''namespace veles {
namespace messages {
'''
    namespace_specific_end = ''.join(
        ['}}  // namespace {}\n'.format(i) for i in namespaces[-2::-1]])
    namespace_messages_end = '''}  // namespace messages
}  // namespace veles
'''
    header_cpp = namespace_specific_start
    header_fwd = namespace_specific_start
    source_conv = namespace_messages_start
    source_cpp = namespace_specific_start
    for name, el in source.__dict__.items():
        if name.isupper() and isinstance(el, int):
            header_cpp += 'const int64_t {0} = {1};\n'.format(name, el)

    enums = [el for el in source.__dict__.values() if inspect.isclass(el)
             and issubclass(el, enumeration.EnumModel)
             and el.__module__ == source.__name__]

    for el in enums:
        header_fwd += 'enum class {};\n'.format(el.cpp_type()[0])

    for el in getattr(source, '_models', []):
        header_fwd += 'class {};\n'.format(el.cpp_type()[0])

    header_fwd += namespace_specific_end

    for el in getattr(source, '_models', []):
        if len(el.cpp_type()) > 2:
            namespaces = el.cpp_type()[1].split('::')
            for ns in namespaces[:-1]:
                header_fwd += 'namespace {} {{\n'.format(ns)
            header_fwd += 'class {};\n'.format(namespaces[-1])
            for ns in namespaces[-2::-1]:
                header_fwd += '}}  // namespace {}\n'.format(ns)

    header_fwd += namespace_messages_start

    for el in enums:
        header_cpp += el.generate_header_code()
        header_fwd += el.generate_header_conv_code()
        source_conv += el.generate_source_conv_code()

    for el in getattr(source, '_models', []):
        if issubclass(el, model.PolymorphicModel) and el.object_type is None:
            header_cpp += el.generate_base_header_code()
            source_cpp += el.generate_base_source_code()
            header_fwd += el.generate_base_header_conv_code()
            source_conv += el.generate_base_source_conv_code()
        else:
            header_cpp += el.generate_header_code()
            source_cpp += el.generate_source_code()
            header_fwd += el.generate_header_conv_code()
            source_conv += el.generate_source_conv_code()
    header_cpp += namespace_specific_end
    source_cpp += namespace_specific_end
    source_conv += namespace_messages_end
    header_fwd += namespace_messages_end

    return header_fwd, header_cpp, source_cpp+source_conv


if __name__ == '__main__':
    generate_cpp_code()
